﻿/* Copyright (C) 2007-2010 STS Soft

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; version 2 of the License.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

using System;
using System.Collections.Generic;
using STSdb.General.Reflection;
using System.Collections;
using STSdb.Files;
using System.Reflection;

namespace STSdb.Data
{
    public class XTable : ITransaction, IEnumerable<Row<object, object>>
    {
        private Delegate tryGetDelegate;
        private MethodReflector tryGetDefaultMethodReflector;
        private MethodReflector clearMethodReflector;
        private MethodReflector closeMethodReflector;
        private MethodReflector existsMethodReflector;
        private MethodReflector findMethodReflector;
        private MethodReflector findNextMethodReflector;
        private MethodReflector findPrevMethodReflector;
        private MethodReflector findAfterMethodReflector;
        private MethodReflector findBeforeMethodReflector;
        private MethodReflector deleteMethodReflector;
        private MethodReflector deleteMethodReflectorFromKeyToKey;
        private MethodReflector mergeMethodReflector;
        private MethodReflector changeMethodReflector;
        private MethodReflector swapMethodReflector;
        private MethodReflector flushMethodReflector;

        private MethodReflector forwardMethodReflector;
        private MethodReflector forwardMethodReflectorFromKey;
        private MethodReflector forwardMethodReflectorFromIndexToIndex;
        private MethodReflector forwardMethodReflectorFromKeyToKey;
        private MethodReflector forwardGroupMethodReflectorIndex;
        private MethodReflector forwardGroupMethodReflectorKey;

        private MethodReflector backwardMethodReflector;
        private MethodReflector backwardMethodReflectorToKey;
        private MethodReflector backwardMethodReflectorToIndexFromIndex;
        private MethodReflector backwardMethodReflectorToKeyFromKey;
        private MethodReflector backwarGroupdMethodReflectorIndex;
        private MethodReflector backwarGroupdMethodReflectorKey;

        private NestedDefaultPropertyReflector thisIndexMethodReflector;

        private PropertyHelper rowKeyPropertyHelper;
        private PropertyHelper rowRecordPropertyHelper;

        private PropertyHelper countPropertyHelper;
        private PropertyHelper versionPropertyHelper;
        private PropertyHelper recordPersistPropertyHelper;
        private PropertyHelper keyMapPropertyHelper;
        private PropertyHelper cacheCapacityPropertyHelper;
        private PropertyHelper blockCapacityPropertyHelper;
        private PropertyHelper firstRowPropertyHelper;
        private PropertyHelper lastRowPropertyHelper;
        private PropertyHelper systemSizePropertyHelper;
        private PropertyHelper dataSizePropertyHelper;
        private PropertyHelper sizePropertyHelper;
        private PropertyHelper syncRootPropertyHelper;
        
        public readonly object xTable;
        public readonly Type xTableType;

        public readonly RawFile RawFile;
        public readonly ulong Offset;

        public XTable(object xTable)
        {
            if (xTable == null)
                throw new ArgumentNullException("table");

            this.xTable = xTable;
            xTableType = xTable.GetType();

            RawFile = (RawFile)xTableType.GetField("RawFile").GetValue(xTable);
            Offset = (ulong)xTableType.GetField("Offset").GetValue(xTable);

            rowKeyPropertyHelper = new PropertyHelper(RowType, "Key");
            rowRecordPropertyHelper = new PropertyHelper(RowType, "Record");
        }

        #region Manage

        public object this[object key]
        {
            get
            {
                if (thisIndexMethodReflector == null)
                    thisIndexMethodReflector = new NestedDefaultPropertyReflector(xTableType, string.Empty);

                return thisIndexMethodReflector.GetValue(xTable, key);
            }
            set
            {
                if (thisIndexMethodReflector == null)
                    thisIndexMethodReflector = new NestedDefaultPropertyReflector(xTableType, string.Empty);

                thisIndexMethodReflector.SetValue(xTable, key, value);
            }
        }

        public void Delete(object key)
        {
            if (deleteMethodReflector == null)
                deleteMethodReflector = new MethodReflector(xTableType, "Delete", KeyType);

            deleteMethodReflector.Call(xTable, key);
        }

        public void Delete(object firstKey, object lastKey)
        {
            if (deleteMethodReflectorFromKeyToKey == null)
                deleteMethodReflectorFromKeyToKey = new MethodReflector(xTableType, "Delete", KeyType, KeyType);

            deleteMethodReflectorFromKeyToKey.Call(xTable, firstKey, lastKey);
        }

        public void Clear()
        {
            if (clearMethodReflector == null)
                clearMethodReflector = new MethodReflector(xTableType, "Clear");

            clearMethodReflector.Call(xTable);
        }

        public void Close()
        {
            if (closeMethodReflector == null)
                closeMethodReflector = new MethodReflector(xTableType, "Close");

            closeMethodReflector.Call(xTable);
        }

        public void Merge(object table)
        {
            if (mergeMethodReflector == null)
                mergeMethodReflector = new MethodReflector(xTableType, "Merge", xTable.GetType());

            mergeMethodReflector.Call(xTable, table);
        }

        public void Change(object key, object newKey)
        {
            if (changeMethodReflector == null)
                changeMethodReflector = new MethodReflector(xTableType, "Change", new Type[] { KeyType, KeyType });

            changeMethodReflector.Call(xTable, key, newKey);
        }

        public void Swap(object key1, object key2)
        {
            if (swapMethodReflector == null)
                swapMethodReflector = new MethodReflector(xTableType, "Swap", new Type[] { KeyType, KeyType });

            swapMethodReflector.Call(xTable, key1, key2);
        }

        #endregion

        #region Find Methods

        private delegate bool TryGetDelegate<TKey, TRecord>(TKey key, out TRecord record);

        public bool TryGet(object key, out object record)
        {
            if (tryGetDelegate == null)
            {
                MethodInfo mi = xTableType.GetMethod("TryGet", BindingFlags.Public | BindingFlags.Instance);
                Type openType = typeof(TryGetDelegate<,>);
                Type closeType = openType.MakeGenericType(KeyType, RecordType);
                tryGetDelegate = Delegate.CreateDelegate(closeType, xTable, mi, true);
            }

            object[] args = new object[] { key, null };
            bool result = (bool)tryGetDelegate.DynamicInvoke(args);
            //bool result = (bool)xTableType.InvokeMember("TryGet", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, xTable, args);
            record = args[1];

            return result;
        }

        public object TryGetDefault(object key, object defaultRecord)
        {
            if (tryGetDefaultMethodReflector == null)
                tryGetDefaultMethodReflector = new MethodReflector(xTableType, "TryGetDefault", KeyType, RecordType);

            return tryGetDefaultMethodReflector.CallFunc(xTable, key, defaultRecord);
        }

        public object Find(object key)
        {
            if (findMethodReflector == null)
                findMethodReflector = new MethodReflector(xTableType, "Find", KeyType);

            return findMethodReflector.CallFunc(xTable, key);
        }

        public bool Exists(object key)
        {
            if (existsMethodReflector == null)
                existsMethodReflector = new MethodReflector(xTableType, "Exists", KeyType);

            return (bool)existsMethodReflector.CallFunc(xTable, key);
        }

        public object FindNext(object key)
        {
            if (findNextMethodReflector == null)
                findNextMethodReflector = new MethodReflector(xTableType, "FindNext", KeyType);

            return findNextMethodReflector.CallFunc(xTable, key);
        }

        public object FindPrev(object key)
        {
            if (findPrevMethodReflector == null)
                findPrevMethodReflector = new MethodReflector(xTableType, "FindPrev", KeyType);

            return findPrevMethodReflector.CallFunc(xTable, key);
        }

        public object FindAfter(object key)
        {
            if (findAfterMethodReflector == null)
                findAfterMethodReflector = new MethodReflector(xTableType, "FindAfter", KeyType);

            return findAfterMethodReflector.CallFunc(xTable, key);
        }

        public object FindBefore(object key)
        {
            if (findBeforeMethodReflector == null)
                findBeforeMethodReflector = new MethodReflector(xTableType, "FindBefore", KeyType);

            return findBeforeMethodReflector.CallFunc(xTable, key);
        }

        #endregion

        #region Enumerators

        public IEnumerable<Row<object, object>> Forward(byte[] firstIndex, byte[] lastIndex)
        {
            if (forwardMethodReflectorFromIndexToIndex == null)
                forwardMethodReflectorFromIndexToIndex = new MethodReflector(xTableType, "Forward", typeof(byte[]), typeof(byte[]));

            var enumerator = forwardMethodReflectorFromIndexToIndex.CallFunc(xTable, firstIndex, lastIndex);

            foreach (var row in (IEnumerable)enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        public IEnumerable<Row<object, object>> Backward(byte[] lastIndex, byte[] firstIndex)
        {
            if (backwardMethodReflectorToIndexFromIndex == null)
                backwardMethodReflectorToIndexFromIndex = new MethodReflector(xTableType, "Backward", typeof(byte[]), typeof(byte[]));

            var enumerator = backwardMethodReflectorToIndexFromIndex.CallFunc(xTable, lastIndex, firstIndex);

            foreach (var row in (IEnumerable)enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        public IEnumerable<Row<object, object>> Forward(object firstKey, object lastKey)
        {
            if (forwardMethodReflectorFromKeyToKey == null)
                forwardMethodReflectorFromKeyToKey = new MethodReflector(xTableType, "Forward", KeyType, KeyType);

            var enumerator = forwardMethodReflectorFromKeyToKey.CallFunc(xTable, firstKey, lastKey);

            foreach (var row in (IEnumerable)enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        public IEnumerable<Row<object, object>> Backward(object lastKey, object firstKey)
        {
            if (backwardMethodReflectorToKeyFromKey == null)
                backwardMethodReflectorToKeyFromKey = new MethodReflector(xTableType, "Backward", KeyType, KeyType);

            var enumerator = backwardMethodReflectorToKeyFromKey.CallFunc(xTable, lastKey, firstKey);

            foreach (var row in (IEnumerable)enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        public IEnumerable<Row<object, object>> Forward(object firstKey)
        {
            if (forwardMethodReflectorFromKey == null)
                forwardMethodReflectorFromKey = new MethodReflector(xTableType, "Forward", KeyType);

            var enumerator = forwardMethodReflectorFromKey.CallFunc(xTable, firstKey);

            foreach (var row in (IEnumerable)enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        public IEnumerable<Row<object, object>> Backward(object lastKey)
        {
            if (backwardMethodReflectorToKey == null)
                backwardMethodReflectorToKey = new MethodReflector(xTableType, "Backward", KeyType);

            var enumerator = backwardMethodReflectorToKey.CallFunc(xTable, lastKey);

            foreach (var row in (IEnumerable)enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        public IEnumerable<Row<object, object>> Forward()
        {
            if (forwardMethodReflector == null)
                forwardMethodReflector = new MethodReflector(xTableType, "Forward");

            var enumerator = forwardMethodReflector.CallFunc(xTable);

            foreach (var row in (IEnumerable)enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        public IEnumerable<Row<object, object>> Backward()
        {
            if (backwardMethodReflector == null)
                backwardMethodReflector = new MethodReflector(xTableType, "Backward");

            var enumerator = backwardMethodReflector.CallFunc(xTable);

            foreach (var row in (IEnumerable)enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        public IEnumerable<Row<object, object>> ForwardGroup(byte[] index)
        {
            if (forwardGroupMethodReflectorIndex == null)
                forwardGroupMethodReflectorIndex = new MethodReflector(xTableType, "ForwardGroup", typeof(byte[]));

            var enumerator = forwardGroupMethodReflectorIndex.CallFunc(xTable, index);

            foreach (var row in (IEnumerable)enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        public IEnumerable<Row<object, object>> BackwardGroup(byte[] index)
        {
            if (backwarGroupdMethodReflectorIndex == null)
                backwarGroupdMethodReflectorIndex = new MethodReflector(xTableType, "BackwardGroup", typeof(byte[]));

            var enumerator = backwarGroupdMethodReflectorIndex.CallFunc(xTable, index);

            foreach (var row in (IEnumerable)enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        public IEnumerable<Row<object, object>> ForwardGroup(object key)
        {
            if (forwardGroupMethodReflectorKey == null)
                forwardGroupMethodReflectorKey = new MethodReflector(xTableType, "ForwardGroup", KeyType);

            var enumerator = forwardGroupMethodReflectorKey.CallFunc(xTable, key);

            foreach (var row in (IEnumerable)enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        public IEnumerable<Row<object, object>> BackwardGroup(object key)
        {
            if (backwarGroupdMethodReflectorKey == null)
                backwarGroupdMethodReflectorKey = new MethodReflector(xTableType, "BackwardGroup", KeyType);

            var enumerator = backwarGroupdMethodReflectorKey.CallFunc(xTable, key);

            foreach (var row in (IEnumerable)enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        #endregion

        #region IEnumerable<Row<object,object>> Members

        public IEnumerator<Row<object, object>> GetEnumerator()
        {
            var enumerator = (IEnumerable)xTable;

            foreach (var row in enumerator)
                yield return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        #endregion

        #region Cache

        public void Flush()
        {
            if (flushMethodReflector == null)
                flushMethodReflector = new MethodReflector(xTableType, "Flush");

            flushMethodReflector.Call(xTable);
        }

        #endregion

        #region Properties

        public ushort Version
        {
            get
            {
                if (versionPropertyHelper == null)
                    versionPropertyHelper = new PropertyHelper(xTableType, "Version");

                return versionPropertyHelper.GetUInt16(xTable);
            }
        }

        public ulong Count
        {
            get
            {
                if (countPropertyHelper == null)
                    countPropertyHelper = new PropertyHelper(xTableType, "Count");

                return countPropertyHelper.GetUInt64(xTable);
            }
        }

        public object RecordPersist
        {
            get
            {
                if (recordPersistPropertyHelper == null)
                    recordPersistPropertyHelper = new PropertyHelper(xTableType, "RecordPersist");

                return recordPersistPropertyHelper.GetValue(xTable);
            }
            set
            {
                if (recordPersistPropertyHelper == null)
                    recordPersistPropertyHelper = new PropertyHelper(xTableType, "RecordPersist");

                recordPersistPropertyHelper.SetValue(xTable, value);
            }
        }

        public object KeyMap
        {
            get
            {
                if (keyMapPropertyHelper == null)
                    keyMapPropertyHelper = new PropertyHelper(xTableType, "KeyMap");

                return keyMapPropertyHelper.GetValue(xTable);
            }
            set
            {
                if (keyMapPropertyHelper == null)
                    keyMapPropertyHelper = new PropertyHelper(xTableType, "KeyMap");

                keyMapPropertyHelper.SetValue(xTable, value);
            }
        }

        public uint CacheCapacity
        {
            get
            {
                if (cacheCapacityPropertyHelper == null)
                    cacheCapacityPropertyHelper = new PropertyHelper(xTableType, "CacheCapacity");

                return cacheCapacityPropertyHelper.GetUInt32(xTable);
            }
            set
            {
                if (cacheCapacityPropertyHelper == null)
                    cacheCapacityPropertyHelper = new PropertyHelper(xTableType, "CacheCapacity");

                cacheCapacityPropertyHelper.SetValue(xTable, value);
            }
        }

        public uint BlockCapacity
        {
            get
            {
                if (blockCapacityPropertyHelper == null)
                    blockCapacityPropertyHelper = new PropertyHelper(xTableType, "BlockCapacity");

                return blockCapacityPropertyHelper.GetUInt32(xTable);
            }
            set
            {
                if (blockCapacityPropertyHelper == null)
                    blockCapacityPropertyHelper = new PropertyHelper(xTableType, "BlockCapacity");

                blockCapacityPropertyHelper.SetValue(xTable, value);
            }
        }

        public Row<object, object> FirstRow
        {
            get
            {
                if (firstRowPropertyHelper == null)
                    firstRowPropertyHelper = new PropertyHelper(xTableType, "FirstRow");

                object row = firstRowPropertyHelper.GetValue(xTable);

                return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
            }
        }

        public Row<object, object> LastRow
        {
            get
            {
                if (lastRowPropertyHelper == null)
                    lastRowPropertyHelper = new PropertyHelper(xTableType, "LastRow");

                object row = lastRowPropertyHelper.GetValue(xTable);

                return new Row<object, object>(rowKeyPropertyHelper.GetValue(row), rowRecordPropertyHelper.GetValue(row));
            }
        }

        public ulong SystemSize
        {
            get
            {
                if (systemSizePropertyHelper == null)
                    systemSizePropertyHelper = new PropertyHelper(xTableType, "SystemSize");

                return systemSizePropertyHelper.GetUInt64(xTable);
            }
        }

        public ulong DataSize
        {
            get
            {
                if (dataSizePropertyHelper == null)
                    dataSizePropertyHelper = new PropertyHelper(xTableType, "DataSize");

                return dataSizePropertyHelper.GetUInt64(xTable);
            }
        }

        public ulong Size
        {
            get
            {
                if (sizePropertyHelper == null)
                    sizePropertyHelper = new PropertyHelper(xTableType, "Size");

                return sizePropertyHelper.GetUInt64(xTable);
            }
        }

        public object SyncRoot
        {
            get
            {
                if (syncRootPropertyHelper == null)
                    syncRootPropertyHelper = new PropertyHelper(xTableType, "SyncRoot");

                return syncRootPropertyHelper.GetValue(xTable);
            }
        }

        #endregion

        #region ITransaction Members

        public IEnumerable<JournalItem> Journal()
        {
            return ((ITransaction)xTable).Journal();
        }

        public void Commit()
        {
            ((ITransaction)xTable).Commit();
        }

        public void Rollback()
        {
            ((ITransaction)xTable).Rollback();
        }

        #endregion

        public Type KeyType
        {
            get { return xTableType.GetGenericArguments()[0]; }
        }

        public Type RecordType
        {
            get { return xTableType.GetGenericArguments()[1]; }
        }

        public Type RowType
        {
            get
            {
                Type openType = typeof(Row<,>);
                Type closeType = openType.MakeGenericType(KeyType, RecordType);

                return closeType;
            }
        }
    }
}
